package hotel.quartz.oversea.job.meituan;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import hotel.base.oversea.constant.ApiConstans;
import hotel.base.oversea.constant.CommonConstants;
import hotel.base.oversea.constant.CommonUrlConstants;
import hotel.base.oversea.constant.MeituanConstants;
import hotel.base.oversea.constant.quartz.UrlMtEnums;
import hotel.base.oversea.utils.Result;
import hotel.quartz.oversea.config.service.RedisService;
import hotel.quartz.oversea.entity.form.JobForm;
import hotel.quartz.oversea.job.base.BaseJob;
import hotel.quartz.oversea.service.JobAndTriggerService;
import hotel.quartz.oversea.util.JobRestTemplateUtils;
import hotel.quartz.oversea.util.MapUtils;
import hotel.quartz.oversea.vo.meituan.MtHotelRealRoomVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 *  Meituan Hotel Room Job
 * </p>
 *
 * @description: Meituan Hotel Room Job
 * @author: qiuyy
 * @date: 2020-05-26 13:22
 * @version: V1.0
 */
@Slf4j
public class MeituanHotelRoomJob implements BaseJob {
    private String timestamp = "Mhrj"+String.valueOf(System.currentTimeMillis());
    private String jobGroupName = MeituanConstants.JOB_GROUP_NAME_MT;
    private String jobClassName = this.getClass().getName();
    private String redisMtHotelRoomWaitDataSaveKey = MeituanConstants.REDIS_MT_HOTEL_ROOM_INFO_WAIT_SAVE;
    private int redisWaitCount = MeituanConstants.REDIS_MT_HOTEL_ROOM_INFO_WAIT_COUNT;
    private String redisMtHotelRoomJobIdsHas = MeituanConstants.REDIS_MT_HOTEL_ROOM_JOB_IDS_HAS;
    private String redisMtHotelRoomJobIdsNow = MeituanConstants.REDIS_MT_HOTEL_ROOM_JOB_IDS_NOW;
    private int idsPageSize = ApiConstans.MT_SELECT_PAGE_SIZE;
    @Autowired
    private JobAndTriggerService jobAndTriggerService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisService redisService;
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 1、请求接口1：酒店分页ids接口，结合redis和定时器每五秒执行，实现分页查询
     * 2、请求接口2：根据酒店物理房型，接口支持最多20个酒店
     * 结果：根据酒店ids数组查询酒店的物理房型信息，可能为空
     *
     * @param jobExecutionContext
     */
    @Override
    @SneakyThrows
    public void execute(JobExecutionContext jobExecutionContext) {
        log.info("Meituan Hotel Room Job {} >>>>>>>> 酒店物理房型接口执行时间: {}",timestamp, DateUtil.now());// 0/10 0 17 * * ?    */10 * * * * ?

        //正式：ip+端口+网关+mapping地址?access_token//本地联调：ip+端口+mapping地址?access_token
        String host = CommonUrlConstants.MEITUAN_HOST;
        //美团接口，获取物理房型
        String hotelRoomUrl = host + UrlMtEnums.hotelRoom.GET.getValue();//+ "?access_token="+accessToken;
        //美团接口，保存物理房型
        String saveRoomUrl = host + UrlMtEnums.hotelRoom.SAVE.getValue();// + "?access_token="+accessToken;
        boolean check = checkSaveRedisRoomData(saveRoomUrl);
        if(!check){
            //获取20条酒店数据，无则暂停定时器
            List<Long> ids = getNowIds();
            if(null != ids && ids.size()>0){
                //请求物理房型接口，上游接口限制20个酒店
                requestRoomApi(hotelRoomUrl,saveRoomUrl,ids);
            }
        }

        log.info("Meituan Hotel Room Job {} >>>>>>>> 酒店物理房型接口结束时间: {}",timestamp, DateUtil.now());
    }

    private boolean checkSaveRedisRoomData(String saveRoomUrl) {
        log.info("Meituan Hotel room Job {} 酒店物理房型接口进入方法 checkSaveRedisRoomData",timestamp);
        Map<Long,List<MtHotelRealRoomVo>> getInfos = getRedisHotelRoomWaitData();
        if(null != getInfos && getInfos.size()>=redisWaitCount*idsPageSize){
            //判断条件根据存入数据格式
            //缓存酒店数据达到大于等于count*pageSize，则执行保存到数据库操作，可能大于count*pageSize 比如数据操作保存失败还原加入到redis
            boolean result = requestSaveRedisWaitHotelRoomDataApi(saveRoomUrl,getInfos);
            if(result){
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

    private boolean requestSaveRedisWaitHotelRoomDataApi(String saveRoomUrl,Map<Long, List<MtHotelRealRoomVo>> redisHotelRoomInfosMap) {
        log.info("Meituan Hotel room Job {} 酒店物理房型接口接口进入方法 requestSaveRedisWaitHotelInfos",timestamp);
        if(null == redisHotelRoomInfosMap){
            //getRedisHotelRoomWaitData 有串行的可能，上次执行还没结束，这次执行，上次删除getRedisHotelRoomWaitData，这次进入提交getRedisHotelRoomWaitData，
            // 但是getRedisHotelRoomWaitData为空，所以返回false
            log.info("Meituan Hotel room Job {} 酒店物理房型接口接口requestSaveRedisWaitHotelRoomDataApi方法redisHotelRoomInfosMap 数据为空，不执行上送接口: {}",timestamp);
            return false;
        }
        Map map = new HashMap();
        map.put("realRoomInfos",redisHotelRoomInfosMap);
        //调用前,删除当前缓存的酒店数据，让下一次定时器正常查询上游接口。若调用结果失败则还原加入缓存
        deleteRedisHotelWaitSaveRoomInfos();
        //发起请求
        Result response = JobRestTemplateUtils.post(saveRoomUrl, JSON.toJSONString(map),restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            log.info("Meituan Hotel room Job {} 酒店物理房型 requestSaveRedisWaitHotelRoomDataApi 保存数据成功",timestamp);
            return true;
        }else{
            log.error("Meituan Hotel room Job {} 酒店物理房型 requestSaveRedisWaitHotelRoomDataApi 异常错误: {}",timestamp,response);
            backPutRedisHotelRoomInfos(redisHotelRoomInfosMap);
            return false;
        }
    }

    /**
     * 获取当前需查询ids
     * getRedisAllHotelIds   所有的酒店ids
     * getRedisHasHotelIds   已查询过的ids
     * getRedisNowHotelIds   当前待定时器查询ids
     * @return
     */
    public List<Long> getNowIds(){
        Map<String,Object> nowIds = getRedisNowHotelIds();
        if(null == nowIds || nowIds.size()==0){
            nowIds = new HashMap<>();
            //重新获取
            Map<String,Object> allIdsMap = getRedisAllHotelIds();
            if(null != allIdsMap && allIdsMap.size()>0){
                Map<String,Object> hasIdsMap = getRedisHasHotelIds();

                if(null == hasIdsMap){
                    int i = 1;
                    for (Map.Entry<String, Object> entry : allIdsMap.entrySet()) {
                        if(i<=idsPageSize){
                            nowIds.put(entry.getKey(),entry.getValue());
                        }else{
                            break;
                        }
                        i += 1;
                    }
                }else{
                    if(null != allIdsMap && allIdsMap.size()>0){
                        int i = 1;
                        Map<String, Object> resultMap = MapUtils.getDifferenceSet(allIdsMap,hasIdsMap);
                        if(null != resultMap && resultMap.size()>0){
                            for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
                                if(i<=idsPageSize){
                                    nowIds.put(entry.getKey(),entry.getValue());
                                }else{
                                    break;
                                }
                                i += 1;
                            }
                        }else{
                            log.info("Meituan Hotel room Job {} 酒店物理房型 getDifferenceSet is null");
                            endingJob(true);
                            return null;
                        }
                    }
                }
                //预先把子酒店ids加入已完成查询，后面查询出现问题则需要还原移出“hasIdsMap”
                putHasIdsMap(nowIds);
                setRedisNowHotelIds(nowIds);
            }else{
                endingJob(false);
                log.error("Switch Basic Room Type Job 暂停定时器！携程子酒店id获取异常错误,getRedisAllHotelIds is null");
                return null;
            }
        }else{
        }
        List<Long> list = new ArrayList<>();
        for(Map.Entry<String,Object> entry:nowIds.entrySet()){
            list.add(Long.valueOf(entry.getKey()));
        }
        return list;
    }

    /**
     * 加入 某酒店ids被查询标识
     * @param nowIds
     */
    public void putHasIdsMap(Map nowIds){
        Map<String,Object> has = getRedisHasHotelIds();
        if(null == has){
            has = new HashMap<>();
        }
        has.putAll(nowIds);
        setRedisHasHotelIds(has);
        log.info("Switchs Hotel Master Job {} 酒店物理房型 putHasIdsMap has.size:{}",timestamp,has.size());
    }

    /**
     * 移出 某酒店ids被查询标识
     * @param idsMap
     */
    public void removeHasIdsMap(Map<String,Object> idsMap){
        Map<String,Object> has = getRedisHasHotelIds();
        if(null != has){
            for(Map.Entry<String,Object> entry:idsMap.entrySet()){
                has.remove(entry.getKey());
            }
        }
        setRedisHasHotelIds(has);
        log.info("Switchs Hotel Master Job {} 酒店物理房型 removeHasIdsMap has.size:{}",timestamp,has.size());
    }


    /**
     * 请求酒店物理房型接口
     * 最多支持20个酒店
     * @param detailUrl
     * @param ids
     */
    public void requestRoomApi(String detailUrl,String saveUrl,List<Long> ids){
        Map paramsMap = new HashMap();
        paramsMap.put("hotelIds",ids);
        String paramsJson = JSON.toJSONString(paramsMap);
        log.info("Meituan Hotel Room Job {} 获取物理房型进入方法 requestRoomApi: {}",timestamp,paramsJson);
        //发起请求
        Result response = JobRestTemplateUtils.post(detailUrl,paramsJson,restTemplate);
        if(response.getCode()== CommonConstants.SUCCESS){
            if(null != response.getData()){
                String hotelDetails = JSON.toJSONString(response.getData());
                Map<Long, List<MtHotelRealRoomVo>> mapRealRoomVoMap = null;
                try {
                    mapRealRoomVoMap = (Map<Long, List<MtHotelRealRoomVo>>) JSON.parseObject(hotelDetails, Map.class);
                } catch (Exception e) {
                    log.error("Meituan Hotel room Job {} 获取酒店物理房型异常错误 parse error !!: {},{}",timestamp,response, JSON.toJSONString(response),response, JSON.toJSONString(response));
                    log.error(e.getMessage(),e);
                }
                if (null != mapRealRoomVoMap && mapRealRoomVoMap.size() > 0) {
                    //存入redis，
                    boolean setResultRedis = savePutRedisHotelRoomInfos(mapRealRoomVoMap);
                    if(setResultRedis){
                        if(ids.size()<idsPageSize){
                            //执行完当前，根据上送的酒店数组数量，判断已无下一页，保存redis数据，结束定时器
                            boolean re = requestSaveRedisWaitHotelRoomDataApi(saveUrl,getRedisHotelRoomWaitData());
                            if(re){
                                successPutHasIdsMap();
                            }else{
                                //getRedisHotelRoomWaitData 有串行的可能，上次执行还没结束，这次执行，上次删除getRedisHotelRoomWaitData，这次进入提交getRedisHotelRoomWaitData，
                                // 但是getRedisHotelRoomWaitData为空，所以返回false
                                //log.error("Meituan Hotel room Job {} 最后一页保存操作 异常错误: {},{}",timestamp,response,paramsJson);
                            }
                        }else{
                            //调用接口成功后更新,下次执行下一批数据
                            successPutHasIdsMap();
                        }
                    }else{
                        removeHasIdsMap(getRedisNowHotelIds());
                        log.error("Meituan Hotel room Job {} 酒店物理房型进入方法 savePutRedisHotelInfos 缓存set异常错误，缓存失败！当前次定时器执行无效！",timestamp);
                    }
                } else {
                    //可能为空
                    //removeHasIdsMap(getRedisNowHotelIds());
                    log.error("Meituan Hotel room Job {} 获取酒店物理房型  mapRealRoomVoMap null: {},{}",timestamp,response, JSON.toJSONString(response),response, JSON.toJSONString(paramsMap));
                }
            }else{
                //数据可能为空，结束当前批次数组酒店，执行下一组
                successPutHasIdsMap();
            }
        }else{
            //不更新index，定时器再次执行的还是上一次页码数据
            removeHasIdsMap(getRedisNowHotelIds());
            log.error("Meituan Hotel Room Job {} 获取酒店物理房型接口异常错误，更新保存详情不成功，不更新index: {},{}",timestamp,response,paramsJson);
        }
    }
    /**
     * 成功请求酒店房型接口后，合法酒店房型数据，追加进缓存中
     */
    private boolean savePutRedisHotelRoomInfos(Map<Long, List<MtHotelRealRoomVo>> mapRealRoomVoMap) {
        Map<Long,List<MtHotelRealRoomVo>> getRedisMap = (Map<Long,List<MtHotelRealRoomVo>>)redisService.get(redisMtHotelRoomWaitDataSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(mapRealRoomVoMap);
        }else{
            getRedisMap = mapRealRoomVoMap;
        }
        boolean result = redisService.set(redisMtHotelRoomWaitDataSaveKey,getRedisMap);
        return result;
    }
    /**
     * 更新redis已操作的ids，判断是否结束定时器，
     */
    public void successPutHasIdsMap(){
        deleteRedisNowHotelIds();
        //判断是否已无下一个需要查询，则暂停定时器
        Map<String,Object> allCityIdsMap = getRedisAllHotelIds();
        Map<String, Object>  has = getRedisHasHotelIds();
        if(null != has && has.size()>0){
            Map<String, Object> resultMap = MapUtils.getDifferenceSet(allCityIdsMap,getRedisHasHotelIds());
            if(null == resultMap || resultMap.size()==0) {
                //已无待查询
                endingJob(true);
            }
        }
    }
    /**
     * 结束定时器
     * @param delKey
     */
    public void endingJob(boolean delKey){
        //已最后一页，暂停定时器，删除rediskey
        JobForm jobForm = new JobForm();
        jobForm.setJobClassName(jobClassName);
        jobForm.setJobGroupName(jobGroupName);
        try {
            jobAndTriggerService.pauseJob(jobForm);
            log.warn("Meituan Hotel Room Job 最后一页暂停: {}",jobForm);
            if(delKey){
                deleteRedisHasIds();
            }
        } catch (SchedulerException e) {
            log.error("Meituan Hotel Room Job 最后一页暂停job异常: {}",jobForm);
            log.error(e.getMessage(),e);
        }
    }
    public void setRedisHasHotelIds(Map<String,Object> updateHas){
        redisTemplate.opsForValue().set(redisMtHotelRoomJobIdsHas,updateHas);
    }
    public void deleteRedisNowHotelIds(){
        redisTemplate.delete(timestamp+redisMtHotelRoomJobIdsNow);
    }
    public void deleteRedisHasIds(){
        redisTemplate.delete(redisMtHotelRoomJobIdsHas);
    }
    public Map<String,Object> getRedisHasHotelIds(){
        return (Map<String,Object>)redisTemplate.opsForValue().get(redisMtHotelRoomJobIdsHas);
    }
    public Map<String,Object> getRedisAllHotelIds(){
        return (Map<String,Object>)redisTemplate.opsForValue().get(MeituanConstants.REDIS_MT_HOTEL_IDS);
    }
    public void setRedisNowHotelIds(Map<String,Object> newIds){
        redisService.set(timestamp+redisMtHotelRoomJobIdsNow,newIds,3600L);
    }
    public Map<String,Object> getRedisNowHotelIds(){
        return (Map<String,Object>)redisService.get(timestamp+redisMtHotelRoomJobIdsNow);
    }
    private Map<Long, List<MtHotelRealRoomVo>> getRedisHotelRoomWaitData() {
        return (Map<Long,List<MtHotelRealRoomVo>>)redisService.get(redisMtHotelRoomWaitDataSaveKey);
    }
    /**
     * 删除缓存的待保存酒店物理房型数据
     * @return
     */
    public void deleteRedisHotelWaitSaveRoomInfos(){
        log.info("Meituan Hotel room Job {} 酒店物理房型接口进入方法 deleteRedisHotelRoomInfos 删除当前酒店物理房型数据缓存 REDIS_MT_HOTEL_ROOM_INFO_WAIT_SAVE",timestamp);
        redisTemplate.delete(redisMtHotelRoomWaitDataSaveKey);
    }
    /**
     * 还原追加回去待保存酒店数据
     * @return
     */
    public void backPutRedisHotelRoomInfos(Map<Long,List<MtHotelRealRoomVo>> redisHotelInfosMap){
        log.info("Meituan Hotel room Job {} 酒店物理房型进入方法 backPutRedisHotelRoomInfos 还原追加当前酒店物理房型存缓存 REDIS_MT_HOTEL_DATA_WAIT_SAVE",timestamp);
        //还原redis数据
        Map<Long,List<MtHotelRealRoomVo>> getRedisMap = (Map<Long,List<MtHotelRealRoomVo>>)redisService.get(redisMtHotelRoomWaitDataSaveKey);
        if(null != getRedisMap && getRedisMap.size()>0){
            getRedisMap.putAll(redisHotelInfosMap);
        }else{
            getRedisMap = redisHotelInfosMap;
        }
        redisService.set(redisMtHotelRoomWaitDataSaveKey,getRedisMap);
    }

}
